﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Diagnostics;

namespace BReusable.StaticReflection
{

	/// <summary>
	/// Class compile time safe type walking or databinding
	/// using static reflection
	/// Handles:
	///		PropertyName (binding vs nonbinding) (static vs instanced)
	///		FieldName (binding vs nonbinding) (static vs instanced)
	///		VariableName
	///		FunctionName (static vs instanced)
	/// </summary>
	/// <seealso 
	/// cref="http://stackoverflow.com/questions/2596026/what-are-the-use-cases-for-this-static-reflection-code"/>
	/// <seealso 
	/// cref="http://stackoverflow.com/questions/1329138/how-to-make-databinding-type-safe-and-support-refactoring"/>
	/// <remarks>
	/// Handles:
	/// PropertyName (binding vs nonbinding) (static vs instanced)
	/// FieldName (binding vs nonbinding) (static vs instanced)
	/// VariableName
	/// FunctionName (static vs instanced)
	/// </remarks>
	public static class Member
	{
		private static string GetMemberName(Expression expression, bool includeSuper)
		{
			switch (expression.NodeType)
			{
				case ExpressionType.MemberAccess:
					var memberExpression = (MemberExpression)expression;
					if (includeSuper == false)
						return memberExpression.Member.Name;
					var supername = GetMemberName(memberExpression.Expression, includeSuper);

					if (String.IsNullOrEmpty(supername))
						return memberExpression.Member.Name;

					return String.Concat(supername, '.', memberExpression.Member.Name);

				case ExpressionType.Call:
					var callExpression = (MethodCallExpression)expression;
					return callExpression.Method.Name;

				case ExpressionType.Convert:
					var unaryExpression = (UnaryExpression)expression;
					return GetMemberName(unaryExpression.Operand, includeSuper);

				case ExpressionType.Parameter:
					//Parameter : foo => foo
					//So this is for actual parameters of the lambda
					return String.Empty;
				case ExpressionType.Lambda: //ImaginaryDevelopment addition for method names
					var lambdaExpression = (LambdaExpression)expression;
					return GetMemberName(lambdaExpression.Body, includeSuper);
				case ExpressionType.Constant:
					//failed to find a way to get a constant's name
					//return expression.ToString();
				default:
					throw new ArgumentException("The expression walk failed on unsupported node type:"+
					expression.NodeType);
			}
		}



		/// <summary>
		/// Get the name needed for databinding for a Property
		/// If the expression is T.PropertyName.SubPropertyName
		/// It would return PropertyName.SubPropertyName
		/// This is necessary for proper databinding
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static string ValueBindingName<T, TResult>(Expression<Func<T, TResult>> expression)
		{
			return GetMemberName(expression.Body, true);
		}

		/// <summary>
		/// Gets the name of the Property or field
		/// Does not include ExpressionName used in DataBinding
		/// T.PropertyName1.PropertyName2 returns PropertyName2
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static string ValueName<T, TResult>(Expression<Func<T, TResult>> expression)
		{
			return GetMemberName(expression.Body, false);
		}
		/// <summary>
		/// Get the name of a function on a class
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static string MethodName<T>(Expression<Func<T, object>> expression)
		{
			return GetMemberName(expression.Body, false);
		}

        public static string MethodName<T, TResult>(Expression<Func<T, TResult>> expression)
        {
            return GetMemberName(expression.Body, false);
        }
		/// <summary>
		/// Get the name of a procedure on a class
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static string MethodName<T>(Expression<Action<T>> expression)
		{
			return GetMemberName(expression.Body, false);
		}

		/// <summary>
		/// Get the name of a variable (local, parameter, global, etc...)
		/// Should be a simple ()=> variable;
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static string VariableName(Expression<Func<object>> expression)
		{
			return GetMemberName(expression.Body, false);
		}

		/// <summary>
		/// Get the name of a static method
		/// </summary>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static string StaticMethodName(Expression<Func<object>> expression)
		{
			return GetMemberName(expression, false);
		}

		public static string StaticMethodName(Expression<Action> expression)
		{
			return ((MemberExpression)expression.Body).Member.Name;
		}

		#region Extensions

		/// <summary>
		/// Get a property, field or function(methods that return a value) name on a class
		/// T is usually inferred
		/// Does Properties, Fields, Methods with a return value
		/// T.ClassMemberName(t=>t.Property);
		/// T.ClassMemberName(t=>t.Field);
		/// </summary>
		/// <typeparam name="T">Usually inferred</typeparam>
		/// <param name="sourceType"></param>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static string ValueName<T>(this T sourceType, Expression<Func<T, object>> expression)
		{
			return GetMemberName(expression.Body, false);
		}
		public static string ValueBindingName<T>(this T sourceType, Expression<Func<T, object>> expression)
		{
			return GetMemberName(expression.Body, true);
		}
		/// <summary>
		/// Get the name of a Function (non-statics)
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="sourceType"></param>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static string ClassMemberName<T,TResult>(this T sourceType, Expression<Func<T,TResult>> expression)
		{
			return GetMemberName(expression.Body, false);
		}

        /// <summary>
        /// Get the name of a Procedure (non-statics)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sourceType"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static string ClassMemberName<T>(this T sourceType, Expression<Action<T>> expression)
        {
            return GetMemberName(expression.Body, false);
        }

		/// <summary>
		/// Get the name needed for databinding for a Property
		/// If the expression is T.PropertyName.SubPropertyName
		/// It would return PropertyName.SubPropertyName
		/// This is necessary for proper databinding
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static string ClassMemberBindingName<T>(this T sourceType, Expression<Func<T, object>> expression)
		{
			return GetMemberName(expression.Body, true);
		}


		/// <summary>
		/// Gets the name of a property, field or method (with a return type) 
		/// for The type inside an IEnumerable 
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="sourceList"></param>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static string TMemberName<T>(this IEnumerable<T> sourceList, Expression<Func<T, object>> expression)
		{
			return GetMemberName(expression.Body, false);
		}

		#endregion

		#region Deadcode



		///// <summary>
		///// Gets the name of the Property for databinding
		///// includes the walking list needed for databinding
		///// T can not usually be inferred
		///// Sample usages:
		///// Member.Name&lt;T&gt;(t=>t.Property)
		///// Member.Name&lt;T&gt;(t=>t.PropertyA.SubPropertyB)
		///// </summary>
		///// <typeparam name="T"></typeparam>
		///// <param name="expression"></param>
		///// <returns></returns>
		//public static string BindingName<T>(Expression<Func<T, object>> expression)
		//{
		//    return GetMemberName(expression.Body, true);
		//}

		///// <summary>
		///// Gets the name of a Property,field or Function
		///// Does not include ExpressionName used in DataBinding
		///// T.PropertyName1.PropertyName2 returns PropertyName2
		///// </summary>
		///// <typeparam name="T"></typeparam>
		///// <param name="expression"></param>
		///// <returns></returns>
		//public static String Name<T>(Expression<Func<T, object>> expression)
		//{
		//    return GetMemberName(expression.Body, false);
		//}



		///// <summary>
		///// Used for class members when no instance is present
		///// MethodNames with no return value
		///// 
		///// </summary>
		///// <typeparam name="T"></typeparam>
		///// <param name="expression"></param>
		///// <returns></returns>
		//public static string Name<T>(Expression<Action<T>> expression)
		//{
		//    return GetMemberName(expression.Body, false);
		//}




		#endregion


		///// <summary>
		///// Variable names
		///// 
		///// </summary>
		///// <param name="expression"></param>
		///// <returns></returns>
		//public static string VariableName(Expression<Action> expression)
		//{
		//    return GetMemberName(expression.Body, false);
		//}

		/// <summary>
		/// Get the name of a static field or property
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="fieldNameExpression"></param>
		/// <returns></returns>
		public static string StaticValueName(Expression<Func<object>> fieldNameExpression)
		{
			//return ((MemberExpression)fieldNameExpression.Body).Member.Name;
			return GetMemberName(fieldNameExpression, false);
		}

	}

}
